// Executor.java

/**
 *      Copyright (C) 2008 10gen Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package com.mongodb.jdbc;

import java.io.StringReader;
import java.util.List;
import java.util.regex.Pattern;

import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.DateValue;
import net.sf.jsqlparser.expression.DoubleValue;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.TimeValue;
import net.sf.jsqlparser.expression.TimestampValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.Between;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.drop.Drop;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.update.Update;

import com.mongodb.BasicDBObject;
import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.jdbc.MongoFieldMapping.FieldInfo;
import com.mongodb.jdbc.MongoSQLException.BadSQL;

public class Executor {
	
    static final boolean D = false;

    Executor( MongoConnection conn, String sql )
        throws MongoSQLException {
    	_conn = conn;
        _sql = sql;
        _statement = parse( sql );

        if ( D ) System.out.println( sql );
    }
    
    void setParams( List params ){
        _pos = 1;
        _params = params;
    }

    /**
     * 查询，返回游标
     * @return
     * @throws MongoSQLException
     */
    DBCursor query()
        throws MongoSQLException {
        if ( ! ( _statement instanceof Select ) )
            throw new IllegalArgumentException( "not a query sql statement" );
        
        Select select = (Select)_statement;
        if ( ! ( select.getSelectBody() instanceof PlainSelect ) )
            throw new UnsupportedOperationException( "can only handle PlainSelect so far" );
        
        PlainSelect ps = (PlainSelect)select.getSelectBody();
        if ( ! ( ps.getFromItem() instanceof Table ) )
            throw new UnsupportedOperationException( "can only handle regular tables" );
        
        DBCollection coll = getCollection( (Table)ps.getFromItem() );
        
        {//查找映射字段
        	if (_conn.fieldMappingMap != null) {
        		String namespace = _conn.getDB().getName() + "." + coll.getName();
        		lastFieldMapping = _conn.fieldMappingMap.get(namespace);
        		if (lastFieldMapping == null) {//如果没有完整域名，还会尝试只有集合名的
        			lastFieldMapping = _conn.fieldMappingMap.get(coll.getName());
        		}
        	}
        }
        		
        // fields
        BasicDBObject fields = new BasicDBObject();
        for ( Object o : ps.getSelectItems() ){
            SelectItem si = (SelectItem)o;
            if ( si instanceof AllColumns ){
                if ( fields.size() > 0 )
            		throw new UnsupportedOperationException( "can't have * and fields" );
            }
            else if ( si instanceof SelectExpressionItem ){
                SelectExpressionItem sei = (SelectExpressionItem)si;
                if (sei.getExpression() instanceof Function) {//函数
                	Function function = (Function)sei.getExpression();
//                	System.out.println(function.getName());
				} else {//字段
					fields.put( toFieldName( sei.getExpression() ) , 1 );
				}
            }
            else {
                throw new UnsupportedOperationException( "unknown select item: " + si.getClass() );
            }
        }
        
        // where
        DBObject query = parseWhere( ps.getWhere() );
        
        // done with basics, build DBCursor
        if ( D ) {
        	System.out.println( "\t" + "table: " + coll );
        }
        
        if ( D ){  	
        	System.out.println( "\t" + "fields: " + fields );
        }
        
        if ( D ){
        	System.out.println( "\t" + "query : " + query );
        } 
        
        DBCursor c = coll.find( query , fields );
//        DBCursor c = null;
//        {//distinct
//        	if (ps.getDistinct() != null) {
////				ps.getDistinct().getOnSelectItems();
//        		System.out.println(coll.distinct("b").getClass());
//        		for (Object obj : coll.distinct("b")) {
//					System.out.println(obj.getClass());
//				}
////        		c = new DBCursor(coll,coll.distinct("b",query),query,coll.getReadPreference());
//        	}
//        }
        
        
        { // order by
            List<OrderByElement> orderBylist = ps.getOrderByElements();
            if ( orderBylist != null && orderBylist.size() > 0 ){
                BasicDBObject order = new BasicDBObject();
                for ( int i=0; i<orderBylist.size(); i++ ){
                    OrderByElement o = orderBylist.get(i);
                    order.put( getFieldName(o.getExpression().toString()) , o.isAsc() ? 1 : -1 );
                }
                c.sort( order );
            }
        }
        
        
        {//top
        	if (ps.getTop() != null) {
        		int topRow = (int) ps.getTop().getRowCount();
        		c.limit(topRow);
			}
        }
        
        
        {//LIMIT ? OFFSET ?
        	if (ps.getLimit() != null) {
        		int offset = (int) ps.getLimit().getOffset();
        		c.skip(offset);
        		int row = (int) ps.getLimit().getRowCount();
        		c.limit(row);
			}
        }

        return c;
    }

    int writeop()
        throws MongoSQLException {
        
        if ( _statement instanceof Insert )
            return insert( (Insert)_statement );
        else if ( _statement instanceof Update )
            return update( (Update)_statement );
        else if ( _statement instanceof Delete )
            return delete( (Delete)_statement );
        else if ( _statement instanceof Drop )
            return drop( (Drop)_statement );

        throw new RuntimeException( "unknown write: " + _statement.getClass() );
    }
    
    
    DBObjAndCollection writeopAddBat()
    	throws MongoSQLException {
    	if ( _statement instanceof Insert )
    		return insert2( (Insert)_statement );

    	throw new RuntimeException( "unknown write: " + _statement.getClass() );
    }
    
    int insert( Insert in )
        throws MongoSQLException {

        if ( in.getColumns() == null )
            throw new MongoSQLException.BadSQL( "have to give column names to insert" );
        DBObjAndCollection DAC = insert2( in );
        DAC.getColl().insert(DAC.getDbObj());
        //checkException( DAC.getColl().insert(DAC.getDbObj()));        
        return 1; // TODO - this is wrong
    }
    
//    static int insert3(DBObject dbObj){
//    	  DBCollection coll = getCollection( in.getTable() );
//
//          checkException(coll.insert(dbObj).getLastError());        
//          return 1;
//    }
    
    DBObjAndCollection insert2( Insert in ) throws BadSQL{
    	DBObjAndCollection DAC = new DBObjAndCollection();
        BasicDBObject o = new BasicDBObject();
        DBCollection coll = getCollection( in.getTable() );
        
        List valueList = ((ExpressionList)in.getItemsList()).getExpressions();
        if ( in.getColumns().size() != valueList.size() )
            throw new MongoSQLException.BadSQL( "number of values and columns have to match" );

        for ( int i=0; i<valueList.size(); i++ ){
            o.put( in.getColumns().get(i).toString() , toConstant( (Expression)valueList.get(i) ) );

        }
        DAC.setColl(coll);
        DAC.setDbObj(o);
        return DAC;
    }

    int update( Update up )
        throws MongoSQLException {
        
        DBObject query = parseWhere( up.getWhere() );
        
        BasicDBObject set = new BasicDBObject();
        
        for ( int i=0; i<up.getColumns().size(); i++ ){
            String k = up.getColumns().get(i).toString();
            Expression v = (Expression)(up.getExpressions().get(i));
            set.put( k.toString() , toConstant( v ) );
        }

        DBObject mod = new BasicDBObject( "$set" , set );

        DBCollection coll = getCollection( up.getTables().get(0) );
        coll.update( query , mod );
        //checkException(coll.update( query , mod ));
        return 1; // TODO
    }
    
    int delete( Delete delete ) throws MongoSQLException {
    	DBObject query = parseWhere( delete.getWhere() );
    	DBCollection coll = getCollection( delete.getTable() );
    	coll.remove(query);
    	//checkException(coll.remove(query));
    	return 1; // TODO
    }

    int drop( Drop d ){
        DBCollection c = _conn.getCollection( d.getName().getName() );
        c.drop();
        return 1;
    }

    // ---- helpers -----

    String toFieldName( Expression e ){
        if ( e instanceof StringValue )
            return e.toString();
        if ( e instanceof Column ){
        	Column column = (Column)e;
        	String columnName = column.getColumnName();
//        	if (lastFieldMapping != null) {
//        		FieldInfo fieldInfo = lastFieldMapping.getDbFieldInfo(columnName);
//        		return fieldInfo == null ? columnName : fieldInfo.columnName;
//			} else {
//				return columnName;
//			}
        	return getFieldName(columnName);
        }
        throw new UnsupportedOperationException( "can't turn [" + e + "] " + e.getClass() + " into field name" );
    }
    /**
     * 真实字段名转换
     * @param columnName 查询字段名
     * @return 返回转换后的字段名
     */
    String getFieldName(String columnName){
    	if (lastFieldMapping != null) {
    		FieldInfo fieldInfo = lastFieldMapping.getDbFieldInfo(columnName);
    		return fieldInfo == null ? columnName : fieldInfo.columnName;
		} else {
			return columnName;
		}
    }

    /**
     * 获取值对象
     * @param e 表达式对象
     * @return
     */
    Object toConstant( Expression e ){
        if ( e instanceof StringValue ) {
            return ((StringValue) e).getValue();
        } else if ( e instanceof DoubleValue ) {
            return ((DoubleValue) e).getValue();
    	} else if ( e instanceof LongValue ) {
            return ((LongValue) e).getValue();
        } else if (e instanceof DateValue) {
        	return ((DateValue) e).getValue();
		} else if (e instanceof TimestampValue) {
        	return ((TimestampValue) e).getValue();
		} else if (e instanceof TimeValue) {
        	return ((TimeValue) e).getValue();
		} else if ( e instanceof NullValue ) {
            return null;
		} else if ( e instanceof JdbcParameter ) {
            return _params.get( _pos++ );
		}
                 
        throw new UnsupportedOperationException( "can't turn [" + e + "] " + e.getClass().getName() + " into constant " );
    }

    /**
     * 解析where条件表达式
     * @param e
     * @return 
     */
    DBObject parseWhere( Expression e ){
        BasicDBObject o = new BasicDBObject();
        if ( e == null )
            return o;

        parseConditional(e,o);

        return o;
    }
    /**
     * 解释and，or，括号条件
     * @param e
     * @param o
     */
    void parseConditional(Expression e, BasicDBObject o){

        if (e instanceof AndExpression){//and
        	parseConditional(((BinaryExpression) e).getLeftExpression(),o);
        	parseConditional(((BinaryExpression) e).getRightExpression(),o);
		} else if (e instanceof OrExpression){//or
			BasicDBObject[] objArr = new BasicDBObject[]{new BasicDBObject(),new BasicDBObject()};
        	parseConditional(((BinaryExpression) e).getLeftExpression(),objArr[0]);
        	parseConditional(((BinaryExpression) e).getRightExpression(),objArr[1]);
        	o.put("$or", objArr);
		} else if (e instanceof Parenthesis) {//括号
			parseConditional(((Parenthesis) e).getExpression(),o);
		} else {
			parseRelational(e,o);
		}
    }
    /**
     * 解析表达式关系，例如：=,>,>=,<,<=,<>,between,in等
     * @param e 
     * @param o 
     */
    void parseRelational(Expression e, BasicDBObject o){
    	if ( e instanceof EqualsTo ){//等于
            EqualsTo eq = (EqualsTo)e;
            o.put( toFieldName( eq.getLeftExpression() ) , toConstant( eq.getRightExpression() ) );
        } else if (e instanceof NotEqualsTo) {//不等于
        	NotEqualsTo net = (NotEqualsTo)e;
        	DBObject obj = new BasicDBObject();
        	obj.put("$ne", toConstant( net.getRightExpression() ));
        	o.put(toFieldName( net.getLeftExpression() ) , obj );
		} else if (e instanceof GreaterThan) {//大于
        	GreaterThan gt = (GreaterThan)e;
//        	DBObject obj = new BasicDBObject();
//        	obj.put("$gt", toConstant( gt.getRightExpression() ));
//        	o.put(toFieldName( gt.getLeftExpression() ) , obj );
        	String fieldName = toFieldName( gt.getLeftExpression());
			String key = "$gt";
			Object value = toConstant( gt.getRightExpression() );
			if (!isSameField(o,fieldName,key,value)) {
				DBObject obj = new BasicDBObject();
				obj.put(key, value);
				o.put(toFieldName( gt.getLeftExpression() ) , obj );
			}
        	
		} else if (e instanceof GreaterThanEquals) {//大于等于
			GreaterThanEquals gte = (GreaterThanEquals)e;
			String fieldName = toFieldName( gte.getLeftExpression());
			String key = "$gte";
			Object value = toConstant( gte.getRightExpression() );
			if (!isSameField(o,fieldName,key,value)) {
				DBObject obj = new BasicDBObject();
				obj.put(key, value);
				o.put(toFieldName( gte.getLeftExpression() ) , obj );
			}
		} else if (e instanceof MinorThan) {//小于
			MinorThan mt = (MinorThan)e;
//        	DBObject obj = new BasicDBObject();
//        	obj.put("$lt", toConstant( mt.getRightExpression() ));
//        	o.put(toFieldName( mt.getLeftExpression() ) , obj );
        	String fieldName = toFieldName( mt.getLeftExpression());
			String key = "$lt";
			Object value = toConstant( mt.getRightExpression() );
			if (!isSameField(o,fieldName,key,value)) {
				DBObject obj = new BasicDBObject();
				obj.put(key, value);
				o.put(toFieldName( mt.getLeftExpression() ) , obj );
			}
        	
		} else if (e instanceof MinorThanEquals) {//小于等于
			MinorThanEquals mte = (MinorThanEquals)e;
			String fieldName = toFieldName( mte.getLeftExpression());
			String key = "$lte";
			Object value = toConstant( mte.getRightExpression() );
			if (!isSameField(o,fieldName,key,value)) {
				DBObject obj = new BasicDBObject();
				obj.put(key, value);
				o.put(toFieldName( mte.getLeftExpression() ) , obj );
			}
		} else if (e instanceof Between) {//between范围
			Between bt = (Between)e;
        	DBObject obj = new BasicDBObject();
        	obj.put("$gte", toConstant( bt.getBetweenExpressionStart() ));
        	obj.put("$lte", toConstant( bt.getBetweenExpressionEnd() ));
        	o.put(toFieldName( bt.getLeftExpression() ) , obj );
		} else if (e instanceof InExpression) {//in和not in
			InExpression ie = (InExpression)e;
			ExpressionList el = (ExpressionList)ie.getRightItemsList();
			List<Expression> valueList = el.getExpressions();
			Object[] inArr = new Object[valueList.size()];
			int i = 0;
			for (Object value : valueList) {
				inArr[i++] = toConstant((Expression)value);
			}
			DBObject obj = new BasicDBObject();
			if (ie.isNot()) {//not in
				obj.put("$nin", inArr);
			}else {//in
				obj.put("$in", inArr);
			}
        	o.put(toFieldName( ie.getLeftExpression() ) , obj );
		} else if (e instanceof LikeExpression) {//like模糊查询
			LikeExpression le = (LikeExpression)e;
			String strPa = ((StringValue)le.getRightExpression()).getValue();
			if (strPa.charAt(0) == '%') {
				strPa = strPa.substring(1);
			}else {
				strPa = "^" + strPa;
			}
			if (strPa.charAt(strPa.length() - 1) == '%') {
				strPa = strPa.substring(0, strPa.length() - 1);
			}else {
				strPa = strPa + "$";
			}
			strPa = strPa.replaceAll("%", ".*");
			Pattern pattern = Pattern.compile(strPa, Pattern.CASE_INSENSITIVE);
        	o.put(toFieldName( le.getLeftExpression() ) , pattern );
		}
        else {
            throw new UnsupportedOperationException( "can't handle: " + e.getClass() + " yet" );
        }
    }
    /**
     * 判断是否已经有相同字段
     * @param o
     * @param fieldName
     * @param key
     * @param value
     * @return
     */
    public boolean isSameField(BasicDBObject o, String fieldName, String key, Object value){
    	BasicDBObject obj = (BasicDBObject)o.get(fieldName);
		if (obj != null) {
			obj.append(key, value);
			return true;
		}
		return false;
    }

    Statement parse( String s )
        throws MongoSQLException {
        s = s.trim();
        
        try {
            return (new CCJSqlParserManager()).parse( new StringReader( s ) );
        }
        catch ( Exception e ){
            e.printStackTrace();
            throw new MongoSQLException.BadSQL( s );
        }
        
    }
    
    /*private void checkException(CommandResult cr) throws MongoSQLException {
    	if (!cr.ok()) {
    		String msg = null;
    		msg = cr.getErrorMessage();
			throw new MongoSQLException(msg);
		}
    }*/

    /**
     * 获取数据库连接
     * @param t
     * @return 返回获取数据库连接
     */
    DBCollection getCollection( Table t ){
        return _conn.getCollection( t.getName() );
    }

    /** 数据库连接 */
    final MongoConnection _conn;
    ///** Ĭ����ݿ� */
    //DB _db;
    /** SQL语句 */
    final String _sql;
    final Statement _statement;
//    DBObject _dbObj;
    
    MongoFieldMapping lastFieldMapping;
    List _params;
    int _pos;
    
    class DBObjAndCollection{
    	private DBCollection coll;
    	private DBObject dbObj;
		public DBCollection getColl() {
			return coll;
		}
		public void setColl(DBCollection coll) {
			this.coll = coll;
		}
		public DBObject getDbObj() {
			return dbObj;
		}
		public void setDbObj(DBObject dbObj) {
			this.dbObj = dbObj;
		}
    }
}
